using System;
using System.Runtime.CompilerServices;
using System.Threading;

namespace Pinwheel.LSerialize
{
    public unsafe interface ILSerializeContext : IDisposable
    {
        int Tag { get; set; }

        int Total { get; set; }

        int Head { get; set; }

        ValueHeader* ValHeader { get; set; }

        ReferenceHeader* RefHeader { get; set; }

        bool OutOfDepth { get; }

        void IncreaseDepth();
        void DecreaseDepth();

        void IncreaseTag();
        void DecreaseTag();

        int EnsureValOffset();
        int EnsureRefOffset();

        void Reset();
    }

    public unsafe struct LSerializeContext : ILSerializeContext
    {
        internal const int DepthLimit = 1000;
        
        [ThreadStatic]
        public static LSerializeContext* Default;
        
        static LSerializeContext()
        {
            InitDefault();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static void InitDefault()
        {
            if (null != Default) return;
            Profiler.BeginSample("LSerializeContext.InitDefault");
            Default = Native.Current.Alloc<LSerializeContext>();
            Default->m_RefId = 0;
            Default->m_Depth = 0;
            Default->m_PtrTag = (int*)Native.Current.AllocAndClear(DepthLimit * sizeof(int));
            Default->m_ptrHead = (int*)Native.Current.AllocAndClear(DepthLimit * sizeof(int));
            Default->m_PtrTotal = (int*)Native.Current.AllocAndClear(DepthLimit * sizeof(int));
            Default->m_PtrSkip = (int*)Native.Current.AllocAndClear(DepthLimit * sizeof(int));
            Default->m_PtrValOffset = (int*)Native.Current.AllocAndClear(DepthLimit * sizeof(int));
            // Default->m_PtrRefOffset = (int*)Native.Current.AllocAndClear(DepthLimit * sizeof(int));
            Default->m_RefHeader = (ReferenceHeader**)Native.Current.AllocAndClear(DepthLimit * sizeof(ReferenceHeader*));
            Default->m_ValHeader = (ValueHeader**)Native.Current.AllocAndClear(DepthLimit * sizeof(ValueHeader*));
            // ResetRefHeader();
            Profiler.EndSample();
        }
        
        private long m_RefId;
        private int m_Depth;
        public bool OutOfDepth => m_Depth >= DepthLimit;

        private int* m_PtrTag;
        private int* m_ptrHead;
        private int* m_PtrTotal;
        private int* m_PtrSkip;
        private int* m_PtrValOffset;
        // private int* m_PtrRefOffset;

        public int Depth => m_Depth;
        
        public int Tag
        {
            get => *(m_PtrTag + m_Depth);
            set => *(m_PtrTag + m_Depth) = value;
        }

        public int Head
        {
            get => *(m_ptrHead + m_Depth);
            set => *(m_ptrHead + m_Depth) = value;
        }

        public int Total
        {
            get => *(m_PtrTotal + m_Depth);
            set => *(m_PtrTotal + m_Depth) = value;
        }

        public int Skip
        {
            get => *(m_PtrSkip + m_Depth);
            set => *(m_PtrSkip + m_Depth) = value;
        }
        
        public int ValOffset
        {
            get => *(m_PtrValOffset + m_Depth);
            set => *(m_PtrValOffset + m_Depth) = value;
        }

        private ValueHeader** m_ValHeader;

        public ValueHeader* ValHeader
        {
            get => *(m_ValHeader + m_Depth);
            set => *(m_ValHeader + m_Depth) = value;
        }
        public ValueHeader* CurValHeader => m_Depth > 0 ? *(m_ValHeader + m_Depth - 1) : null;
        

        private ReferenceHeader** m_RefHeader;

        public ReferenceHeader* RefHeader
        {
            get => *(m_RefHeader + m_Depth);
            set => *(m_RefHeader + m_Depth) = value;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public long IncreaseRefId() => Interlocked.Increment(ref m_RefId);
        // {
        //     m_RefId = Interlocked.Increment(ref m_RefId);
        //     return m_RefId;
        // }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void IncreaseDepth()
        {
            if (m_Depth >= DepthLimit - 1)
                LSerializeException.ThrowContextDepthOutOfRange(m_Depth + 1, DepthLimit);
            m_Depth = Interlocked.Increment(ref m_Depth);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void DecreaseDepth()
        {
            if (0 == m_Depth)
                LSerializeException.ThrowContextDepthOutOfRange(m_Depth - 1, 0);
            m_Depth = Interlocked.Decrement(ref m_Depth);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void IncreaseTag() => Tag = Interlocked.Increment(ref *(m_PtrTag + m_Depth));
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void DecreaseTag() => Tag = Interlocked.Decrement(ref *(m_PtrTag + m_Depth));
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void IncreaseSkip() => Skip = Interlocked.Increment(ref *(m_PtrSkip + m_Depth));
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void DecreaseSkip() => Skip = Interlocked.Decrement(ref *(m_PtrSkip + m_Depth));

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int EnsureValOffset() =>
            Head + sizeof(ReferenceHeader) + ValOffset + (Total - Skip) * (sizeof(ValueHeader) + sizeof(long));

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int EnsureRefOffset() => 
            Head + sizeof(ReferenceHeader) + ValOffset + Total * (sizeof(ValueHeader) + sizeof(long)) + RefHeader->Size;
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void SetValidFieldCount(int count) => Total = Skip = count;

        public LSerializeContext(int depth)
        {
            m_RefId = 0;
            m_Depth = depth;
            m_PtrTag = (int*)Native.Current.AllocAndClear(DepthLimit * sizeof(int));
            m_ptrHead = (int*)Native.Current.AllocAndClear(DepthLimit * sizeof(int));
            m_PtrTotal = (int*)Native.Current.AllocAndClear(DepthLimit * sizeof(int));
            m_PtrSkip = (int*)Native.Current.AllocAndClear(DepthLimit * sizeof(int));
            m_PtrValOffset = (int*)Native.Current.AllocAndClear(DepthLimit * sizeof(int));
            // m_PtrRefOffset = (int*)Native.Current.AllocAndClear(DepthLimit * sizeof(int));
            m_RefHeader = (ReferenceHeader**)Native.Current.AllocAndClear(DepthLimit * sizeof(ReferenceHeader*));
            m_ValHeader = (ValueHeader**)Native.Current.AllocAndClear(DepthLimit * sizeof(ValueHeader*));
            // ResetRefHeader();
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void CheckValHeader()
        {
            if (null == ValHeader) ValHeader = Native.Current.Alloc<ValueHeader>(); 
        }

        // 重置值类型头部信息
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void ResetValHeader(FieldType valType = FieldType.None,
            CollectionType collectionType = CollectionType.None, FieldType KeyType = FieldType.None)
        {
            Profiler.BeginSample("LSerializeContext.ResetValHeader");
            if (OutOfDepth) LSerializeException.ThrowContextDepthOutOfRange(m_Depth, DepthLimit);
            CheckValHeader();
            ValHeader->Tag = (ushort)Tag;
            ValHeader->CollectionType = collectionType;
            ValHeader->KeyType = KeyType;
            ValHeader->ValType = valType;
            Profiler.EndSample();
        }

        // 重置引用类型头部信息
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void ResetRefHeader(bool autogen = false, long refId = 0L, ushort valCount = 0, ushort refCount = 0)
        {
            Profiler.BeginSample("LSerializeContext.ResetRefHeader");
            if (OutOfDepth) LSerializeException.ThrowContextDepthOutOfRange(m_Depth, DepthLimit);
            if (null == RefHeader) RefHeader = Native.Current.Alloc<ReferenceHeader>();
            RefHeader->RefId = (autogen && refId == 0L) ? IncreaseRefId() : refId;
            RefHeader->Size = 0;
            RefHeader->ValCount = valCount;
            RefHeader->RefCount = refCount;
            Profiler.EndSample();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Reset()
        {
            Profiler.BeginSample("LSerializeContext.Reset");
            if (OutOfDepth) LSerializeException.ThrowContextDepthOutOfRange(m_Depth, DepthLimit);
            // Tag = 0;
            // Head = 0;
            // Total = 0;
            // Skip = 0;
            // ValOffset = 0;
            Native.Utils.Clear(m_PtrTag + m_Depth, sizeof(int));
            Native.Utils.Clear(m_ptrHead + m_Depth, sizeof(int));
            Native.Utils.Clear(m_PtrTotal + m_Depth, sizeof(int));
            Native.Utils.Clear(m_PtrSkip + m_Depth, sizeof(int));
            Native.Utils.Clear(m_PtrValOffset + m_Depth, sizeof(int));
            // Native.Utils.Clear(m_PtrRefOffset + m_Depth, sizeof(int));
            Native.Utils.Clear(RefHeader, sizeof(ReferenceHeader));
            // ResetRefHeader();
            Profiler.EndSample();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void ReleaseAllHeader()
        {
            for (m_Depth = 0; m_Depth < DepthLimit; m_Depth++)
            {
                if (null != RefHeader)
                {
                    Native.Current.Free(RefHeader);
                    RefHeader = null;
                }

                if (null != ValHeader)
                {
                    Native.Current.Free(ValHeader);
                    ValHeader = null;
                }

                if (null == RefHeader && null == ValHeader) break;
            }

            if (null != m_RefHeader) Native.Current.Free(m_RefHeader);
            if (null != m_ValHeader) Native.Current.Free(m_ValHeader);
            m_RefHeader = null;
            m_ValHeader = null;
        }

        public void Clear()
        {
            Profiler.BeginSample("LSerializeContext.Reset");
            m_RefId = 0;
            m_Depth = 0;
            // Tag = 0;
            // Head = 0;
            // Total = 0;
            // Skip = 0;
            // ValOffset = 0;
            Native.Utils.Clear(m_PtrTag, DepthLimit * sizeof(int));
            Native.Utils.Clear(m_ptrHead, DepthLimit * sizeof(int));
            Native.Utils.Clear(m_PtrTotal, DepthLimit * sizeof(int));
            Native.Utils.Clear(m_PtrSkip, DepthLimit * sizeof(int));
            Native.Utils.Clear(m_PtrValOffset, DepthLimit * sizeof(int));
            if (null != ValHeader) Native.Utils.Clear(ValHeader, sizeof(ValueHeader));
            if (null != RefHeader) Native.Utils.Clear(RefHeader, sizeof(ReferenceHeader));
            // ResetValHeader();
            // ResetRefHeader();
            Profiler.EndSample();
        }

        public void Dispose()
        {
            m_Depth = 0;
            Native.Current.Free(m_PtrTag);
            Native.Current.Free(m_ptrHead);
            Native.Current.Free(m_PtrTotal);
            Native.Current.Free(m_PtrSkip);
            Native.Current.Free(m_PtrValOffset);
            // Native.Current.Free(m_PtrRefOffset);
            ReleaseAllHeader();
            m_PtrTag = null;
            m_ptrHead = null;
            m_PtrTotal = null;
            m_PtrSkip = null;
            m_PtrValOffset = null;
            // m_PtrRefOffset = null;
        }
    }
}